////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   ParticleContainer.h
//  Version:     v1.00
//  Created:     11/03/2010 by Corey (split out from other files).
//  Compilers:   Visual Studio.NET
//  Description: 
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "ParticleContainer.h"
#include "ParticleEmitter.h"
#include "ParticleEnviron.h"
#include "Particle.h"
#include "ParticleMemory.h"

#include "ICryAnimation.h"
#include "FogVolumeRenderNode.h"

#include <SpuUtils.h>
#include <IJobManager_JobDelegator.h>

#define fMAX_STATIC_BB_RADIUS					4096.f	// Static bounding box above this size forces dynamic BB
#define fMAX_RELATIVE_TAIL_DEVIATION	0.1f




	#define nMAX_ITERATIONS							8

#define fMAX_FRAME_LIMIT_TIME					0.15f		// Max frame time at which to enforce iteration limit

/*
	Scheme for Emitter updating & bounding volume computation.

	Whenever possible, we update emitter particles only on viewing.
	However, the bounding volume for all particles must be precomputed, 
	and passed to the renderer,	for visibility. Thus, whenever possible, 
	we precompute a Static Bounding Box, loosely estimating the possible
	travel of all particles, without actually updating them.

	Currently, Dynamic BBs are computed for emitters that perform physics,
	or are affected by non-uniform physical forces.
*/

//////////////////////////////////////////////////////////////////////////
CParticleContainer::CParticleContainer( CParticleContainer* pParent, CParticleEmitter* pMain, CParticleEffect const* pEffect )
: m_pEffect(&non_const(*pEffect))
, m_nEmitterSequence(0)
, m_pMainEmitter(pMain)
, m_fAgeLastUpdate(0.f)
, m_fAgeStaticBoundsStable(0.f)
, m_fContainerLife(0.f)
, m_Particles(this)
{
	assert(pEffect);
	assert(pEffect->IsActive());
	m_pParams = &m_pEffect->GetParams();
	assert(m_pParams->nEnvFlags & EFF_LOADED);

	m_pParentContainer = pEffect->GetIndirectParent() ? pParent : 0;

	// To do: Invalidate static bounds on updating areas.
	// To do: Use dynamic bounds if in non-uniform areas ??
	m_bbWorld.Reset();
	m_bbWorldStat.Reset();
	m_bbWorldDyn.Reset();

	OnEffectChange();

	m_nChildFlags = 0;
	m_bUnused = false;
	m_bHasDeferredEmitParticles = false;

	m_fMaxParticleFullLife = m_pEffect->GetMaxParticleFullLife();
	
	m_bNeedSpuUpdate = false;
}

CParticleContainer::~CParticleContainer()
{
}

// Compute number of simultaneous active particles per emitter.
static const int nMAX_OVERLAP = 8;

int CParticleContainer::GetReserveCount() const
{
	const ResourceParticleParams& params = GetParams();

	int nCount = int_ceil(params.fCount.GetMaxValue());

	if (params.bGeometryInPieces && params.pStatObj != 0)
	{
		// Emit 1 particle per piece.
		int nPieces = 0;
		for (int i = params.GetSubGeometryCount()-1; i >= 0; i--)
		{
			if (params.GetSubGeometry(i))
				nPieces++;
		}
		nCount *= max(nPieces,1);
	}

	if (!GetParent() && (GetMain().GetSpawnParams().fPulsePeriod != 0.f || params.fPulsePeriod))
	{
		float fPulse = GetMain().GetSpawnParams().fPulsePeriod;
		if (fPulse == 0.f)
		{
			fPulse = fHUGE;
			for_all_ptrs (const CParticleSubEmitter, pE, m_Emitters)
				fPulse = min(fPulse, pE->GetRepeatAge() - pE->GetStartAge());
		}

		// Increase count for overlapping pulsing.
		float fParticleLife = GetMaxParticleFullLife();
		if (fPulse > 0.f && fPulse < fParticleLife)
		{
			if (params.bContinuous)
			{
				float fEmitterLife = params.GetMaxEmitterLife();
				if (fPulse > fEmitterLife)
				{
					float fLdP = floor(fParticleLife/fPulse);
					float fLmP = fParticleLife - fLdP*fPulse;
					float fOverlap = (fLdP * fEmitterLife + min(fLmP,fEmitterLife)) / min(fParticleLife,fEmitterLife);
					nCount = min( int_ceil(fOverlap * nCount), nCount * nMAX_OVERLAP);
				}
			}
			else
			{
				nCount *= min( int_ceil(fParticleLife / fPulse), nMAX_OVERLAP );
			}
		}
	}

	return nCount;
}

void CParticleContainer::AllocParticles()
{
	FUNCTION_PROFILER_SYS(PARTICLE);

	const ResourceParticleParams& params = GetParams();

	int nReserve = GetReserveCount();

	int nMultiply = 1;
	if (params.bContinuous && m_nChildFlags)
	{
		float fSelfLife = params.GetMaxParticleLife();
		float fFullLife = GetMaxParticleFullLife();
		if (fFullLife > fSelfLife)
			nMultiply = int_ceil(div_min(fFullLife, fSelfLife, (float)nMAX_OVERLAP));
	}

	if (m_pParentContainer)
	{
		m_pParentContainer->AllocParticles();
		nMultiply *= max(m_pParentContainer->GetReserveCount(), m_pParentContainer->m_Particles.capacity());
	}
	else
		nReserve = int_ceil(GetMain().GetEmitCountScale() * nReserve);

	nReserve *= nMultiply;

	int nExtra = m_DeferredEmitParticles.size() * nMultiply;
	nReserve = max(nReserve, m_Particles.size() + nExtra);

	// Alloc any new particles. CParticle constructor allocs all associated objects.
	m_Particles.reserve(nReserve);
}

uint32 CParticleContainer::GetEnvironmentFlags() const
{
	return m_nEnvFlags & CParticleManager::Instance()->GetAllowedEnvironmentFlags();
}

void CParticleContainer::OnEffectChange()
{
	m_nEnvFlags = m_pParams->nEnvFlags;
	m_fSortBias = m_pParams->nDrawLast * 0.01f;

	// Mark if a parent effect provides a local target for these particles.
	m_bHasLocalTarget = false;
	if (!m_pParams->TargetAttraction.bIgnore)
	{
		for (IParticleEffect* p = m_pEffect->GetParent(); p; p = p->GetParent())
		{
			if (p->GetParticleParams().eForceGeneration == ParticleForce_Target)
			{
				m_bHasLocalTarget = true;
				break;
			}
		}
	}

	// Do not use coverage buffer culling for 'draw near' or 'on top' particles
	if (m_pParams->bDrawNear || m_pParams->bDrawOnTop)
		GetMain().SetRndFlags(ERF_RENDER_ALWAYS, true);

#if !defined(XENON) && !defined(PS3)
	if (gEnv->IsEditor() && m_Particles.capacity())
	{
		// Kill all particles if sub-allocations are out of date.
		if (m_Particles.empty() || m_Particles.front().NeedReallocation())
			Reset();
	}
#endif

	m_nHistorySteps = m_pParams->fTailLength ? m_pParams->fTailLength.nTailSteps : 0;

	if (m_pParentContainer)
	{
		// Inherit dynamic bounds requirement.
		m_nEnvFlags |= m_pParentContainer->GetEnvironmentFlags() & EFF_DYNAMIC_BOUNDS;
		m_pParentContainer->m_nChildFlags |= m_nEnvFlags;
		if (GetParams().eAttachType == GeomType_Render)
		{
			if (m_pParentContainer->GetParams().pStatObj)
				m_pParentContainer->GetParams().pStatObj->GetExtent(GetParams().eAttachForm);
		}
	}

	// Prevent invalid particle references to old geometry when editing Geometry param.
	if (gEnv->IsEditing() && GetParams().pStatObj)
		*m_ExternalStatObjs.push_back() = GetParams().pStatObj;
}

CParticleSubEmitter* CParticleContainer::AddEmitter( CParticleSource* pSource, CParticleSubEmitter* pParentEmitter )
{
	if (void* pMem = m_Emitters.push_back_new())
	{		
		return new(pMem) CParticleSubEmitter(pParentEmitter, pSource, this);
	}
	return NULL;
}

SPU_NO_INLINE CParticle* CParticleContainer::AddParticle( CParticleSubEmitter* pEmitter )
{
	if (GetParams().Connection)
	{
		// Segregate by emitter, and always in front of previous particles.
		// To do: faster search for multi-emitter containers.
		for (TParticleList::const_traverser pPart(m_Particles); pPart; ++pPart)
		{
			if (pPart->GetEmitterSequence() == pEmitter->GetSequence())
				return pPart.insert();
		}

		// No previous particle for this emitter found.
		return m_Particles.push_front();
	}

	// Highly approximate sort. Place particle at start or end of list,
	// depending on emitter position relative to bounding box center.
	Vec3 const& vEmitPos = pEmitter->GetEmitPos();
	Vec3 vCamPos = GetCamera().GetPosition();
	float fEmitDistSqr = (vCamPos - vEmitPos).GetLengthSquared();

	Vec3 vSortPos = m_bbWorld.GetCenter();
	float fSortBias = GetParams().GetMaxParticleSize();

	if (sqr((vCamPos - vSortPos).GetLength() - fSortBias) > fEmitDistSqr)
		return m_Particles.push_back();
	else
		return m_Particles.push_front();
}

void CParticleContainer::EmitParticle( const EmitParticleData* pData )
{
	// Queue particle emission.
	if (pData)
	{
		*m_DeferredEmitParticles.push_back() = *pData;

		if (pData->pPhysEnt || pData->bHasLocation || pData->bHasVel)
		{
			// Static bounds no longer reliable.
			SetDynamicBounds();
		}
		if (pData->pStatObj)
		{
			// Add reference to StatObj for lifetime of container.
			assert(GetEnvironmentFlags() & REN_GEOMETRY);
			*m_ExternalStatObjs.push_back() = pData->pStatObj;
		}
	}
	else
	{
		// Default particle data.
		m_DeferredEmitParticles.push_back();
	}
	m_bHasDeferredEmitParticles = true;
}

void CParticleContainer::ComputeStaticBounds( AABB& bb, bool bWithSize, float fMaxLife )
{
  FUNCTION_PROFILER_SYS(PARTICLE);

	QuatTS loc = GetMain().GetLocation();
	AABB bbSpawn(Vec3(0.f));
	float fSpawnAngMax = 0.f;

	ResourceParticleParams const &rParams = GetParams();
	if (m_pParentContainer)
	{
		// Expand by parent spawn volume.
		bool bParentSize = rParams.eAttachType != GeomType_None;
		float fMaxParentLife = rParams.GetMaxSpawnDelay();
		if (rParams.bContinuous || rParams.eMoveRelEmitter)
		{
			if (rParams.fEmitterLifeTime)
				fMaxParentLife += rParams.fEmitterLifeTime.GetMaxValue();
			else
				fMaxParentLife = fHUGE;
		}
		m_pParentContainer->ComputeStaticBounds( bbSpawn, bParentSize, fMaxParentLife );
		loc.t = bbSpawn.GetCenter();
		fSpawnAngMax = m_pParentContainer->GetParams().GetMaxRotationAngle();
	}
	else if (GetMain().GetEmitGeom())
	{
		// Expand by attached geom.
		GetAABB(bbSpawn, loc, GetMain().GetEmitGeom());
		loc.t = bbSpawn.GetCenter();
		fSpawnAngMax = gf_PI;
	}

	loc.s = GetMain().GetParticleScale();
	Vec3 vSpawnSize = bbSpawn.GetSize() * 0.5f;
	SPhysEnviron const& PhysEnv = GetMain().GetPhysEnviron();
	SExternForces forces = PhysEnv.m_UniformForces;
	rParams.GetStaticBounds(bb, loc, GetMain().GetSpawnParams().fSpeedScale, vSpawnSize, 0.f, fSpawnAngMax, bWithSize, fMaxLife, forces);

	// Adjust for internal/external target.
	if (!rParams.TargetAttraction.bIgnore)
	{
		ParticleTarget target = GetMain().GetTarget();
		Vec3 vTargetSize(0.f);
		if (m_bHasLocalTarget && !target.bPriority)
		{
			// Local target from parent emitter.
			if (GetDirectEmitter())
			{
				GetDirectEmitter()->GetLocalTarget(target);
			}
			else if (m_pParentContainer && m_pParentContainer->GetParams().eForceGeneration == ParticleForce_Target && !bbSpawn.IsReset())
			{
				target.bTarget = true;
				target.vTarget = bbSpawn.GetCenter();
				vTargetSize = bbSpawn.GetSize() * 0.5f;
			}
		}
		if (target.bTarget)
		{
			AABB bbTarg(target.vTarget, target.fRadius);
			bbTarg.Expand(vTargetSize + bb.GetSize() * 0.5f);
			bb.Add(bbTarg);
		}
	}
}

void CParticleContainer::UpdateState()
{
	UpdateContainerLife();

	if (!GetMain().GetSpawnParams().fPulsePeriod && m_fContainerLife < GetAge())
	{
		// Can free resources of non-parent containers which finish before rest of emitter.
		if (HasParticles() && !GetChildFlags())
			Reset();
		return;
	}

	if (!NeedsDynamicBounds())
	{
		if (m_bbWorldStat.IsReset())
		{
			ComputeStaticBounds(m_bbWorldStat);
			if (m_bbWorldStat.GetRadiusSqr() >= sqr(fMAX_STATIC_BB_RADIUS))
				SetDynamicBounds();
		}
	}

	if (NeedsDynamicBounds())
	{
		AllocParticles();
		UpdateParticles();
	}

	if (NeedsDynamicBounds())
	{
		m_bbWorld = m_bbWorldDyn;
	}
	else
	{
		m_bbWorld = m_bbWorldStat;
		if (!StaticBoundsStable())
			m_bbWorld.Add(m_bbWorldDyn);
	}

	if (!m_bHasDeferredEmitParticles)
		// Free already emitted particles.
		m_DeferredEmitParticles.clear();
}

// To do: Add flags for movement/gravity/wind.
float CParticleContainer::InvalidateStaticBounds()
{
	if (!NeedsDynamicBounds())
	{
		m_bbWorldStat.Reset();
		if (NeedsExtendedBounds() && !m_Particles.empty())
			return m_fAgeStaticBoundsStable = GetAge() + m_pParams->GetMaxParticleLife();
	}
	return 0.f;
}

SPU_NO_INLINE void CParticleContainer::ComputeUpdateContext( SParticleUpdateContext& context, float fUpdateTime )
{
	const ResourceParticleParams& params = GetParams();

	context.fUpdateTime = fUpdateTime;
	context.fMaxLinearStepTime = fHUGE;

	context.nEnvFlags = GetEnvironmentFlags();

	// Emission bounds parameters.
	context.vEmitBox = params.vRandomOffset;
	context.vEmitScale.zero();
	if (params.fOffsetRoundness)
	{
		float fRound = params.fOffsetRoundness * max(max(context.vEmitBox.x, context.vEmitBox.y), context.vEmitBox.z);
		Vec3 vRound( min(context.vEmitBox.x, fRound), min(context.vEmitBox.y, fRound), min(context.vEmitBox.z, fRound) );
		context.vEmitBox -= vRound;
		context.vEmitScale( vRound.x ? 1/vRound.x : 0, vRound.y ? 1/vRound.y : 0, vRound.z ? 1/vRound.z : 0 );
	}

	if (params.bSpaceLoop)
	{
		// Use emission bounding volume.
		AABB bbSpaceLocal = params.GetEmitOffsetBounds();
		if (GetMain().GetEnvFlags() & REN_BIND_CAMERA)
		{
			// Use CameraMaxDistance as alternate space loop size.
			bbSpaceLocal.Add(Vec3(0.f), params.fCameraMaxDistance);

			// Limit bounds to camera frustum.
			bbSpaceLocal.min.y = max(bbSpaceLocal.min.y, +params.fCameraMinDistance);

			// Scale cam dist limits to handle zoom.
			CCamera const& cam = GetCamera();
			float fHSinCos[2]; cry_sincosf( cam.GetHorizontalFov()*0.5f, &fHSinCos[0], &fHSinCos[1] );
			float fVSinCos[2]; cry_sincosf( cam.GetFov()*0.5f, &fVSinCos[0], &fVSinCos[1] );
			float fZoom = 1.f / cam.GetFov();

			float fMin = fHSinCos[1] * fVSinCos[1] * (params.fCameraMinDistance * fZoom);

			bbSpaceLocal.max.CheckMin( Vec3( fHSinCos[0], 1.f, fVSinCos[0] ) * (bbSpaceLocal.max.y * fZoom) );
			bbSpaceLocal.min.CheckMax( Vec3( -bbSpaceLocal.max.x, fMin, -bbSpaceLocal.max.z ) );
		}

		// Compute params for quick space-loop bounds checking.
		QuatTS const& loc = GetMain().GetLocation();
		context.SpaceLoop.vCenter = loc * bbSpaceLocal.GetCenter();
		context.SpaceLoop.vSize = bbSpaceLocal.GetSize() * (0.5f * loc.s);
		Matrix33 mat(loc.q);
		for (int a = 0; a < 3; a++)
		{
			context.SpaceLoop.vScaledAxes[a] = mat.GetColumn(a);
			if (context.SpaceLoop.vSize[a] != 0.f)
				context.SpaceLoop.vScaledAxes[a] /= context.SpaceLoop.vSize[a];
		}
	}

	context.bHasTarget = (HasLocalTarget() || GetMain().GetTarget().bTarget) 
		&& !GetParams().TargetAttraction.bIgnore;

	context.fMinStepTime = min(fUpdateTime, fMAX_FRAME_LIMIT_TIME) / float(nMAX_ITERATIONS);

	// Compute time and distance limits in certain situations.
	context.fMaxLinearDeviation = fHUGE;
	if (GetHistorySteps() > 0)
	{
		context.fMaxLinearDeviation = fMAX_RELATIVE_TAIL_DEVIATION * params.fSize.GetMaxValue();
		context.fMinStepTime = min(context.fMinStepTime, params.fTailLength.GetMaxValue() * 0.5f / GetHistorySteps());
	}
	if (context.nEnvFlags & ENV_COLLIDE_ANY)
	{
		context.fMaxLinearDeviation = min(context.fMaxLinearDeviation, fMAX_COLLIDE_DEVIATION);
	}

	// If random turbulence enabled, limit linear time based on deviation limit.
	// dp(t) = Turb/2 t^(3/2)
	// t = (2 Dev/Turb)^(2/3)
	if (params.fTurbulence3DSpeed)
	{
		context.fMaxLinearStepTime = min(context.fMaxLinearStepTime, powf(2.f * context.fMaxLinearDeviation / params.fTurbulence3DSpeed, 0.666f));
	}

	// If vortex turbulence enabled, find max rotation angle for allowed deviation.
	float fVortexRadius = params.fTurbulenceSize.GetMaxValue() * GetMain().GetParticleScale();
	if (fVortexRadius > context.fMaxLinearDeviation)
	{
		// dev(r, a) = r (1 - sqrt( (cos(a)+1) / 2 ))
		// cos(a) = 2 (1 - dev/r)^2 - 1
		float fCos = sqr(1.f - context.fMaxLinearDeviation  / fVortexRadius) * 2.f - 1.f;
		float fAngle = RAD2DEG(acos(clamp(fCos, -1.f, 1.f)));
		context.fMaxLinearStepTime = div_min(fAngle, fabs(params.fTurbulenceSpeed.GetMaxValue()), context.fMaxLinearStepTime);
	}
}

//////////////////////////////////////////////////////////////////////////
SPU_NO_INLINE void CParticleContainer::UpdateParticles()
{






#if !defined(PS3)
	if (GetCVars()->e_ParticlesDebug & AlphaBit('z'))
		return;
#endif // !PS3

	if (!GetParams().bEnabled)
		return;

	float fAge = GetAge();
	float fUpdateTime = fAge - m_fAgeLastUpdate;
	if (fUpdateTime > 0.f || m_fAgeLastUpdate == 0.f)
	{
	  FUNCTION_PROFILER_CONTAINER(this);

		if (m_pParentContainer)
		{
			m_pParentContainer->UpdateParticles();
		}

		ZeroStruct(m_Counts);

		if (fUpdateTime > 0.f)
		{
			SParticleUpdateContext context;
			ComputeUpdateContext(context, fUpdateTime);

			if (NeedsDynamicBounds() || !StaticBoundsStable()
				|| (GetCVars()->e_ParticlesDebug & AlphaBits('bx')) || GetMain().IsEditSelected())
			{
				// Accumulate dynamic bounds.
				context.pbbDynamicBounds = &m_bbWorldDyn;
				if (NeedsDynamicBounds() || !StaticBoundsStable())
					m_bbWorldDyn.Reset();
			}
			else
				context.pbbDynamicBounds = NULL;

			GetMain().GetPhysEnviron().LockAreas(context.nEnvFlags, 1);

			// Update all particle positions etc, delete dead particles.
			UpdateParticleStates(context);

			GetMain().GetPhysEnviron().LockAreas(context.nEnvFlags, -1);
		}

		m_fAgeLastUpdate = fAge;
	}
}

SPU_NO_INLINE void CParticleContainer::UpdateParticleStates(const SParticleUpdateContext &context)
{
	// Lifetime check adjustment: continuous particles check life at end of frame, to make room for new particles; 
	// non-continuous check at start of frame, to ensure they're seen at least once.
	float fLifetimeCheck = GetParams().bRemainWhileVisible ? -fHUGE : GetParams().bContinuous ? context.fUpdateTime : 0.f;

	for (TParticleList::traverser p(m_Particles); p; )
	{
		if (!p->IsAlive(fLifetimeCheck))
		{
			p->Deactivate();
			p.erase();
			continue;
		}

		p->Update( context, context.fUpdateTime );
		++p;
	}

	// Emit new particles.
	EmitParticles( context );
}

SPU_NO_INLINE void CParticleContainer::EmitParticles( SParticleUpdateContext const& context )
{
	for_all_ptrs (CParticleSubEmitter, e, m_Emitters)
	{
		if (e->IsAlive())
		{
			e->UpdateState();

			// Emit deferred particles.
			if (m_bHasDeferredEmitParticles)
			{
				for_all_ptrs (const EmitParticleData, pEmit, m_DeferredEmitParticles)
					e->EmitParticle( context, 0.f, pEmit );
				m_bHasDeferredEmitParticles = false;
			}

			e->EmitParticles( context );

			e->SetLastLoc();
		}
	}
}

void CParticleContainer::UpdateEffects( const uint32 nEffectFlags )
{
	const uint32 nFlags = nEffectFlags & m_nEnvFlags;

	for_all_ptrs (CParticleSubEmitter, e, m_Emitters)
	{
		if (e->IsAlive())
		{
			e->UpdateState();
			if (nFlags & EFF_FORCE)
				e->UpdateForce();
			if (nFlags & EFF_SOUND)
				e->UpdateSound();
		}
	}
}

/* Water sorting / filtering:

									Effect:::::::::::::::::::::::::::
Emitter	Camera		above					both					below
-------	------		-----					----					-----
above		above			AFTER					AFTER					skip
				below			BEFORE				BEFORE				skip

both		above			AFTER\below		AFTER[\below]	BEFORE\above
				below			BEFORE\below	AFTER[\above]	AFTER\above

below		above			skip					BEFORE				BEFORE
				below			skip					AFTER					AFTER
*/

void CParticleContainer::Render( SRendParams const& RenParams, SPartRenderParams const& PRParams )
{
  FUNCTION_PROFILER_CONTAINER(this);

	const ResourceParticleParams* pParams = &GetParams();
	
	// Individual container distance culling.
	const float fDist = m_bbWorld.GetDistance(PRParams.m_pCamera->GetPosition());
	float fMaxDist = pParams->GetMaxParticleSize() * pParams->fViewDistanceAdjust * PRParams.m_fMaxAngularDensity;

	if (fDist > fMaxDist && !pParams->fMinPixels)
		return;

	IF (pParams->fCameraMaxDistance > 0.f, 0)
	{
		IF (fDist * PRParams.m_pCamera->GetFov() > pParams->fCameraMaxDistance, 0)
			return;
	}

	// Water and indoor culling tests.
	if (!TrinaryMatch(pParams->tVisibleIndoors, GetMain().GetVisEnviron().OriginIndoors()))
		return;
	if (!TrinaryMatch(pParams->tVisibleUnderwater, GetMain().GetPhysEnviron().m_tUnderWater))
		return;

	IF (pParams->eFacing == ParticleFacing_Water, 0)
	{
		if (m_nRenderStackLevel > 0)
			return;

		// hack: special case for when inside amphibious vehicle
		if (Get3DEngine()->GetOceanRenderFlags() & OCR_NO_DRAW)
			return;
	}

	AllocParticles();

	uint32 nRenderFlags = GetEnvironmentFlags() & PRParams.m_nRenFlags;

	if (nRenderFlags & REN_LIGHTS)
	{
		UpdateParticles();
		RenderLights();
	}

	if (nRenderFlags & REN_GEOMETRY)
	{
		UpdateParticles();
		RenderGeometry(RenParams, *PRParams.m_pCamera);
	}
	else if (nRenderFlags & REN_DECAL)
	{
		UpdateParticles();
		RenderDecals();
	}

	if (nRenderFlags & REN_SPRITE)
	{		
		if (!CanThread())
			// Update particles in main thread.
			UpdateParticles();
		else
			SetNeedSpuUpdate(true);

		assert(!m_bbWorld.IsReset());

		SAddParticlesToSceneJob job;

		// Create emitter RenderObject.
		job.pRenderObject = GetIdentityCRenderObject();
		if (!job.pRenderObject)
			return;

		//
		// Set RenderObject.RState and .ObjFlags
		//
		uint32 nObjFlags = pParams->nRenObjFlags & PRParams.m_nRenObjFlags;

		job.pRenderObject->m_RState = uint8(nObjFlags);
		job.pRenderObject->m_ObjFlags |= (nObjFlags & ~0xFF) | RenParams.dwFObjFlags;

		//
		// Set remaining RenderObject data.
		//
		job.pRenderObject->m_fDistance = PRParams.m_fCamDistance;
		job.pRenderObject->m_fSort = -m_fSortBias;
		job.pRenderObject->m_nTextureID = pParams->nTexId;

		// Ambient color for shader incorporates actual ambient lighting, as well as the constant emissive value.
		float fEmissive = pParams->fEmissiveLighting;
		fEmissive *= powf( PRParams.m_fHDRDynamicMultiplier, pParams->fEmissiveHDRDynamic );
		job.pRenderObject->m_II.m_AmbColor = ColorF(fEmissive) + RenParams.AmbientColor * pParams->fDiffuseLighting;

		// Ambient.a is used to modulate dynamic lighting.
		job.pRenderObject->m_II.m_AmbColor.a = pParams->fDiffuseLighting;

		// Dynamic lighting mask.
		job.pRenderObject->m_DynLMMask[m_nRenderThreadListID] = pParams->fDiffuseLighting > 0.f ? RenParams.nDLightMask : 0;

		//
		// Set SRenderObjectData data
		//
    SRenderObjData* pOD = GetRenderer()->EF_GetObjData(job.pRenderObject, true);

		pOD->m_pParticleParams = pParams;

		// Set texture tiling params for particle shader in 1st 4 temp vars.
		assert(sizeof(pOD->m_fTempVars) >= 10*sizeof(float));
		pParams->SetTileInfo(pOD->m_fTempVars, nObjFlags);

		#if PARTICLE_MOTION_BLUR
			if (nObjFlags & FOB_MOTION_BLUR_PARTICLE)
				// Set motion blur params
				pParams->SetMotionBlurInfo(pOD->m_fTempVars+4);
		#endif

		// Set alphatest ref (material alphatest has higher priority if used)
		pOD->m_fTempVars[9] = pParams->fAlphaTest;

		IF (pParams->fHeatScale, 0)
		{
			pOD->m_nVisionScale = MAX_HEATSCALE;
			uint8 nHeatAmount = pParams->fHeatScale.GetCompressedValue();
			pOD->m_nVisionParams = (uint32) (nHeatAmount << 24)| (nHeatAmount << 16)| (nHeatAmount<< 8) | (0);
		}

		pOD->m_FogVolumeContribIdx[0] = PRParams.m_nFogVolumeContribIdx;
		pOD->m_FogVolumeContribIdx[1] = PRParams.m_nFogVolumeContribIdx;

		pOD->m_ShadowCasters = RenParams.m_ShadowMapCasters;

		if (pParams->nEnvFlags & REN_REFRACTION)
		{
			SetScreenBounds(PRParams, *PRParams.m_pCamera, pOD->m_screenBounds);
		}

		//
		// Set remaining SAddParticlesToSceneJob data.
		//
		job.pPVC = this;

		IMaterial* pMatToUse = pParams->pMaterial;
		if (!pMatToUse)
			pMatToUse = CParticleManager::Instance()->GetLightShader();
		job.pShaderItem = &pMatToUse->GetShaderItem();

		job.nRenderElemFlags = pParams->nRenderElemFlags;
		job.bAfterWater = TrinaryMatch(pParams->tVisibleUnderwater, Get3DEngine()->IsCameraUnderWater())
			&& TrinaryMatch(GetMain().GetPhysEnviron().m_tUnderWater, Get3DEngine()->IsCameraUnderWater());

		// Set halfres preference (enums are numerically compatible).
		job.eAllowHalfRes = (SAddParticlesToSceneJob::EHalfResFlag) +pParams->eAllowHalfRes;
		job.nCustomTexId = RenParams.nTextureID;

		GetRenderer()->EF_AddParticlesToScene(job, *PRParams.m_pCamera);
	}
}

void CParticleContainer::SetScreenBounds( SPartRenderParams const &PRParams, const CCamera &cam, uint8 aScreenBounds[4] )
{
	const int32 align16 = (16-1);
	const int32 shift16 = 4;
	int iOut[4];
	int nWidth = cam.GetViewSurfaceX();
	int nHeight = cam.GetViewSurfaceZ();
	const float fBias = 0.0f, fOffset = 0.0f;
	AABB posAABB = AABB(PRParams.m_pCamera->GetPosition(), 1.00f);
	if (!posAABB.IsIntersectBox(m_bbWorld))  // This is mostly to help out the PS3's odd (wrong?)  muzzle flash AABBs
	{
		cam.CalcScreenBounds(&iOut[0], &m_bbWorld, nWidth, nHeight, fBias, fOffset);
		if( ((iOut[2] - iOut[0]) == nWidth) && ((iOut[3] - iOut[1]) == nHeight) )
		{
			iOut[2] += 16;  // Split fullscreen particles and fullscreen geometry. Better to use some sort of ID/Flag, but this will do the job for now
		}
		aScreenBounds[0] = min(iOut[0] >> shift16, (int32)255);
		aScreenBounds[1] = min(iOut[1] >> shift16, (int32)255);
		aScreenBounds[2] = min((iOut[2] + align16) >> shift16, (int32)255);
		aScreenBounds[3] = min((iOut[3] + align16) >> shift16, (int32)255);
	}
	else
	{
		aScreenBounds[0] = 0;
		aScreenBounds[1] = 0;
		aScreenBounds[2] = min((nWidth >> shift16) + 1, (int32)255);
		aScreenBounds[3] = min((nHeight >> shift16) + 1, (int32)255);
	}
}

bool CParticleContainer::CanThread()
{
	uint32 nEnvFlags = SPU_MAIN_PTR(this)->GetEnvironmentFlags();

	if (!(nEnvFlags & REN_THREAD))
		return false;

	// for SPUs also skip physics and other things













	if (m_pParentContainer)
		return m_pParentContainer->CanThread();

	return true;
}

void CParticleContainer::SetDynamicBounds()
{
	m_nEnvFlags |= EFF_DYNAMIC_BOUNDS;
	GetMain().AddEnvFlags(EFF_DYNAMIC_BOUNDS);
}

float CParticleContainer::GetEmitterLife() const
{
	float fEmitterLife = 0.f;
	if (!m_pParentContainer)
	{
		if (m_pParams->fPulsePeriod)
			fEmitterLife = fHUGE;
		else if (!m_Emitters.empty())
			fEmitterLife = m_Emitters.front().GetEndAge();
	}
	else
	{
		fEmitterLife = m_pParentContainer->GetContainerLife();
		if (!m_pParams->bContinuous)
			fEmitterLife = min(fEmitterLife, m_pParentContainer->GetEmitterLife() + m_pParams->GetMaxSpawnDelay());
	}

	fEmitterLife = min(fEmitterLife, GetMain().GetEndAge());
	return fEmitterLife;
}

void CParticleContainer::UpdateContainerLife()
{
	if (CParticleSubEmitter* pDirectEmitter = GetDirectEmitter())
		pDirectEmitter->UpdateState();
	if (m_pParams->bRemainWhileVisible && GetMain().TimeNotRendered() < 0.5f)
		m_fContainerLife = fHUGE;
	else if (!m_pParams->fParticleLifeTime)
		m_fContainerLife = GetMain().GetEndAge();
	else
		m_fContainerLife = GetEmitterLife() + m_pParams->fParticleLifeTime.GetMaxValue();

	// Adjust for restarted emitters.
	float fAge = GetMain().GetAge();
	m_fAgeLastUpdate = min(m_fAgeLastUpdate, fAge);
}

float CParticleContainer::GetAge() const
{
	return GetMain().GetAge();
}

void CParticleContainer::Reset()
{
	// Free all particle and sub-emitter memory.
	m_Particles.clear();
	if (m_pParentContainer)
		m_Emitters.clear();
	m_fAgeLastUpdate = 0.f;
	m_bbWorldDyn.Reset();
}

// Stat functions.
void CParticleContainer::GetCounts( SParticleCounts& counts ) const
{
	counts.EmittersAlloc += 1.f;
	counts.ParticlesAlloc += m_Particles.capacity();

	if (GetTimeToUpdate() == 0.f)
	{
		// Was updated this frame.
		AddArray(FloatArray(counts), FloatArray(m_Counts));
		counts.EmittersActive += 1.f;
		counts.ParticlesActive += m_Particles.size();

		if ((GetEnvironmentFlags() & REN_ANY) && !m_bbWorldDyn.IsReset())
		{
			counts.DynamicBoundsVolume += m_bbWorldDyn.GetVolume();
			counts.StaticBoundsVolume += m_bbWorld.GetVolume();
			if (!m_bbWorld.ContainsBox(m_bbWorldDyn))
			{
				AABB bbDynClip = m_bbWorldDyn;
				bbDynClip.ClipToBox(m_bbWorld);
				float fErrorVol = m_bbWorldDyn.GetVolume() - bbDynClip.GetVolume();
				counts.ErrorBoundsVolume += fErrorVol;
			}
		}
	}
}

// Reference counting redirected to main emitter, as emitter with its containers deleted all at once.
void CParticleContainer::AddRef()
{
	GetMain().AddRef();
}
void CParticleContainer::Release()
{
	assert(GetMain().GetRefCount() > 1);
	GetMain().Release();
}

void CParticleContainer::GetMemoryUsage(ICrySizer* pSizer) const
{
	m_Particles.GetMemoryUsage(pSizer);	
	m_Emitters.GetMemoryUsage(pSizer);
	m_DeferredEmitParticles.GetMemoryUsagePlain(pSizer);
	m_ExternalStatObjs.GetMemoryUsagePlain(pSizer);
	pSizer->AddObject(&m_nEnvFlags, CParticle::GetAllocationSize(this) * m_Particles.capacity());
}

void CParticleContainer::CollectSpuUsage( VecSpuUsageT &vecSpuUsage )
{
	if( m_Particles.size() == 0 )
		return;

	SpuUsage spuUsage( GetEffect()->GetName(), m_Particles.size() );
	bool bRunsOnPPU = false;

	uint32 nEnvFlags = SPU_MAIN_PTR(this)->GetEnvironmentFlags();

	if (!(nEnvFlags & REN_THREAD))
	{
		bRunsOnPPU = true;
		spuUsage.SetCause( "Threading Disabled" );
	}

	if (!bRunsOnPPU && GetMain().GetEmitGeom().m_pChar)
	{
		bRunsOnPPU = true;
		spuUsage.SetCause( "Has CharacterData" );
	}
	
	// don't use non-cacheable nonuniform areas on spu (requieres physics)	
	SPhysEnviron const& PhysEnv = GetMain().GetPhysEnviron();
	if ( !bRunsOnPPU )
	{
		uint32 nNonCachedFlags = PhysEnv.m_nNonCachedFlags & GetEnvironmentFlags();
		if (nNonCachedFlags)
		{
			bRunsOnPPU = true;
			spuUsage.SetCause( nNonCachedFlags & ENV_WATER ? "Has nonuniform water areas" : "Has noncacheable force areas" );
		}
	}

	if (!bRunsOnPPU && GetMain().GetEmitGeom().m_pPhysEnt)
	{
		bRunsOnPPU = true;
		spuUsage.SetCause( "Is Physics Particle" );
	}

	if( m_pParentContainer )
	{
		m_pParentContainer->CollectSpuUsage(vecSpuUsage);
	}

	if( bRunsOnPPU )
		vecSpuUsage.push_back(spuUsage);
}

void CParticleContainer::OffsetPosition(const Vec3 &delta)
{
#ifdef SEG_WORLD
	for_all_ptrs (CParticleSubEmitter, e, m_Emitters)
		e->OffsetPosition(delta);

	//for_all_ptrs_pc (TParticleList, currentParticle, m_Particles)
	//	currentParticle->OffsetPosition(delta);
#endif
}

